package org.aceor.mddal.gateway.sqlparser.visitor;

import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.expr.*;
import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
import com.alibaba.druid.sql.ast.statement.SQLSelectItem;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlOutputVisitor;
import com.alibaba.druid.sql.visitor.SQLASTVisitor;
import com.google.common.base.Objects;
import com.google.common.base.Strings;
import org.aceor.mddal.common.sqlparser.MysqlVisitor;
import org.aceor.mddal.common.sqlparser.ParseContext;
import org.aceor.mddal.common.sqlparser.result.SQLBuilder;
import org.aceor.mddal.common.sqlparser.result.condition.ComparableAND;
import org.aceor.mddal.common.sqlparser.result.condition.ComparableOR;
import org.aceor.mddal.common.sqlparser.result.condition.ComparableUnit;
import org.aceor.mddal.common.sqlparser.result.condition.value.BindingValue;
import org.aceor.mddal.common.sqlparser.result.context.ShardingContext;
import org.aceor.mddal.common.sqlparser.result.object.Column;
import org.aceor.mddal.common.sqlparser.result.object.Table;
import org.aceor.mddal.common.sqlparser.util.SQLParserUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

import static org.aceor.mddal.common.sqlparser.result.condition.ComparableUnit.BinaryOperator;
import static org.aceor.mddal.common.sqlparser.result.condition.ComparableUnit.BinaryOperator.*;

/**
 * Created by lxue on 16/3/21.
 */
public abstract class AbstractMysqlVisitor extends MySqlOutputVisitor implements SQLASTVisitor, MysqlVisitor {

    private static final Logger logger = LoggerFactory.getLogger(AbstractMysqlVisitor.class);

    private final ParseContext parseContext = new ParseContext();

    /**
     * shardingColumns必须根据sql中得到的table,从LogicTable的tableRules map中获取
     */

    public AbstractMysqlVisitor(Appendable appender) {
        super(appender);
    }

    public AbstractMysqlVisitor() {
        super(new SQLBuilder());
        setPrettyFormat(false);
    }

    public boolean visit(SQLExprTableSource sqlExprTableSource) {
        Table table = new Table(sqlExprTableSource.getExpr().toString(), sqlExprTableSource.getAlias());
        parseContext.addTable(table);
        //添加shardingColumns
        //TODO: 这里多表可能有问题.因为现在支持一条sql进行一次分库分表.如果jion另一个表时,通过这个表的key
        // 也能从router中获得分库分表字段,这就over了
        setShardingContext(table.getName());
        printReplaceToken(table.getName());
        if (!Strings.isNullOrEmpty(table.getAlias())) {
            print(' ');
            print(table.getAlias());
        }
        return false;
    }

    private void setShardingContext(String name) {
        ShardingContext shardingContext = parseContext.getTableShardingColumnsMapping().get(name);
        if (null != shardingContext) {
            parseContext.getSqlParsedResult().shardingContext = shardingContext;
            if (!Strings.isNullOrEmpty(shardingContext.dbShardingColumn)) {
                parseContext.addShardingColumn(new Column(shardingContext.dbShardingColumn));
            }
            if (!Strings.isNullOrEmpty(shardingContext.tbShardingColumn)) {
                parseContext.addShardingColumn(new Column(shardingContext.tbShardingColumn));
            }
        }
    }

    @Override
    public boolean visit(SQLPropertyExpr sqlPropertyExpr) {
        if (!(sqlPropertyExpr.getParent() instanceof SQLBinaryOpExpr)
                && !(sqlPropertyExpr.getParent() instanceof SQLSelectItem)) {
            return super.visit(sqlPropertyExpr);
        }
        if (!(sqlPropertyExpr.getOwner() instanceof SQLIdentifierExpr)) {
            return super.visit(sqlPropertyExpr);
        }
        String tableOrAliasName = ((SQLIdentifierExpr) sqlPropertyExpr.getOwner()).getName();
        //只有是表名的时候才替换
        if (parseContext.isTableAlias(tableOrAliasName)) {
            return super.visit(sqlPropertyExpr);
        }
        printReplaceToken(tableOrAliasName);
        print('.');
        print(sqlPropertyExpr.getName());
        return false;
    }

    @Override
    public boolean visit(final SQLInListExpr inListExpr) {
        if (inListExpr.isNot()) {
            throw new UnsupportedOperationException("不支持Not IN语法");
        }
        ComparableUnit or = buildComparableUnit4InList(inListExpr);
        parseContext.addCondition(buildColumnIgnoreAlias(inListExpr.getExpr()), or);
        return checkPrintExprSkipToken(inListExpr.getExpr()) ? false : super.visit(inListExpr);
    }

    /**
     * @param expr
     * @return 是否打印跳过token
     */
    private boolean checkPrintExprSkipToken(SQLExpr expr) {
        if (parseContext.getShardingColumns().contains(buildColumnIgnoreAlias(expr))) {
            printExprSkipToken();
            return true;
        }
        return false;
    }

    private ComparableUnit buildComparableUnit4InList(SQLInListExpr inListExpr) {
        ComparableOR or = new ComparableOR();
        List<SQLExpr> sqlExprList = inListExpr.getTargetList();
        for (SQLExpr expr : sqlExprList) {
            or.addComparableUnit(createComparableUnit(expr, EQUIVALENT));
        }
        return or;
    }

    @Override
    public boolean visit(final SQLBetweenExpr betweenExpr) {
        ComparableUnit and = buildComparableUnit4Between(betweenExpr);
        parseContext.addCondition(buildColumnIgnoreAlias(betweenExpr.testExpr), and);
        return checkPrintExprSkipToken(betweenExpr.testExpr) ? false : super.visit(betweenExpr);
    }

    private ComparableUnit buildComparableUnit4Between(SQLBetweenExpr betweenExpr) {
        ComparableAND and = new ComparableAND();
        //begin
        and.addComparableUnit(createComparableUnit(betweenExpr.beginExpr, GREATER_THAN_OR_EQUAL));
        //end
        and.addComparableUnit(createComparableUnit(betweenExpr.endExpr, LESS_THAN_OR_EQUAL));
        return and;
    }

    @Override
    public boolean visit(SQLBinaryOpExpr sqlBinaryOpExpr) {
        ComparableUnit unit = null;
        switch (sqlBinaryOpExpr.getOperator()) {
            case Equality:
            case NotEqual:
            case GreaterThan:
            case GreaterThanOrEqual:
            case LessThan:
            case LessThanOrEqual:
                unit = handleCommonBinaryOp(sqlBinaryOpExpr);
                break;
            case Like:
            case NotLike:
                unit = createComparableUnit(sqlBinaryOpExpr.getRight(), BinaryOperator.valueof(sqlBinaryOpExpr.getOperator().getName()));
                break;
            case BooleanAnd:
                if (!handleAndBinaryOp(sqlBinaryOpExpr)) {
                    return false;
                }
                break;
            case BooleanOr:
                if (!handleOrBinaryOp(sqlBinaryOpExpr)) {
                    printExprSkipToken();
                    return false;
                }
                break;
            default:
                logger.warn("Fount Unsupported BinaryOperator: " + sqlBinaryOpExpr.getOperator().getName());
                break;
        }
        if (null != unit) {
            parseContext.addCondition(buildColumnIgnoreAlias(sqlBinaryOpExpr.getLeft()), unit);
            return checkPrintExprSkipToken(sqlBinaryOpExpr.getLeft()) ? false : super.visit(sqlBinaryOpExpr);
        }
        return super.visit(sqlBinaryOpExpr);
    }

    private boolean handleAndBinaryOp(SQLBinaryOpExpr sqlBinaryOpExpr) {
        //skip 默认Condition关系都是and
        //TODO: 支持or and混合
        return true;
    }

    /**
     * 只支持一个or(这里如果存在多个or,构建ComparableUnit树太烧脑)
     * 且or两边的表达式类型只能是SQLIdentifierExpr,SQLPropertyExpr,SQLInListExpr,SQLBetweenExpr
     *
     * @param sqlBinaryOpExpr
     */
    private boolean handleOrBinaryOp(SQLBinaryOpExpr sqlBinaryOpExpr) {
        if (!checkOrOperatorSupportExprType(sqlBinaryOpExpr.getLeft())) {
            throw new IllegalArgumentException("OR操作左侧类型(" + sqlBinaryOpExpr.getLeft().getClass().getName() + ")不支持");
        }

        if (!checkOrOperatorSupportExprType(sqlBinaryOpExpr.getRight())) {
            throw new IllegalArgumentException("OR操作右侧类型(" + sqlBinaryOpExpr.getLeft().getClass().getName() + ")不支持");
        }

        /**
         * 4种情况:
         * 1. leftColumnName与rightColumnName相同,且都是分库分表字段        ==> 构建ComparableOR
         * 2. leftColumnName与rightColumnName相同,且都不是分库分表字段      ==>  不构建ComparableOR
         * 3. leftColumnName与rightColumnName不相同,且都不是分库分表字段    ==>  不构建ComparableOR
         * 4. leftColumnName与rightColumnName不相同,且有一个是分库分表字段  ==>  不支持.因为这会导致一部分全表查询加一部分分库分表查询.OR要分语句执行
         */
        if (checkOrOperatorHasSameColumnInLeftAndRight(sqlBinaryOpExpr.getLeft(), sqlBinaryOpExpr.getRight())
                && parseContext.getShardingColumns().contains(buildColumnIgnoreAlias(sqlBinaryOpExpr.getLeft()))) {
            //TODO 采用递归支持多OR
            ComparableOR or = new ComparableOR();
            or.addComparableUnit(buildComparableUnit(sqlBinaryOpExpr.getLeft()));
            or.addComparableUnit(buildComparableUnit(sqlBinaryOpExpr.getRight()));
            parseContext.addCondition(buildColumnIgnoreAlias(sqlBinaryOpExpr.getLeft()), or);
            parseContext.getSqlParsedResult().conditionContext.setHasShardingColumnOrCondition(true);
            return false;
        }
        if (!checkOrOperatorHasSameColumnInLeftAndRight(sqlBinaryOpExpr.getLeft(), sqlBinaryOpExpr.getRight())
                && (parseContext.getShardingColumns().contains(buildColumnIgnoreAlias(sqlBinaryOpExpr.getLeft()))
                || parseContext.getShardingColumns().contains(buildColumnIgnoreAlias(sqlBinaryOpExpr.getRight())))) {
            throw new UnsupportedOperationException("不支持Or操作符两侧,一个列是分库分表字段,一个又不是");
        }
        return true;
    }

    private ComparableUnit buildComparableUnit(SQLExpr expr) {
        if (expr instanceof SQLBinaryOpExpr) {
            return handleCommonBinaryOp((SQLBinaryOpExpr) expr);
        } else if (expr instanceof SQLInListExpr) {
            return buildComparableUnit4InList((SQLInListExpr) expr);
        } else if (expr instanceof SQLBetweenExpr) {
            return buildComparableUnit4Between((SQLBetweenExpr) expr);
        } else {
            throw new IllegalArgumentException("类型不支持");
        }
    }

    /**
     * 考虑 1=o.id | o.id=1 | o.id=p.id(不支持)
     *
     * @param sqlBinaryOpExpr
     * @return
     */
    private ComparableUnit handleCommonBinaryOp(SQLBinaryOpExpr sqlBinaryOpExpr) {
        if (!(isColumn(sqlBinaryOpExpr.getLeft()) ^ isColumn(sqlBinaryOpExpr.getRight()))) {
            //暂时不支持 数值 op 数值 或者 属性 op 属性
            throw new UnsupportedOperationException("不支持二元操作关系 ==> 数值 op 数值 或者 属性 op 属性");
        }
        if (isColumn(sqlBinaryOpExpr.getLeft())) {
            return createComparableUnit(sqlBinaryOpExpr.getRight(), BinaryOperator.valueof(sqlBinaryOpExpr.getOperator().getName()));
        } else {
            //调整方向
            return createComparableUnit(sqlBinaryOpExpr.getLeft(), BinaryOperator.reverseBinaryOp(BinaryOperator.valueof(sqlBinaryOpExpr.getOperator().getName())));
        }
    }

    private boolean isColumn(SQLExpr expr) {
        return expr instanceof SQLIdentifierExpr || expr instanceof SQLPropertyExpr;
    }

    /**
     * check Or操作支持的expr类型(IN,Between,=)主要用于分库分表字段
     *
     * @param expr
     * @return
     */
    private boolean checkOrOperatorSupportExprType(SQLExpr expr) {
        return (expr instanceof SQLBinaryOpExpr && ((SQLBinaryOpExpr) expr).getOperator() == SQLBinaryOperator.Equality) || expr instanceof SQLInListExpr || expr instanceof SQLBetweenExpr;
    }

    /**
     * check OR操作符左右两侧的column是否一致
     * 不一致的话无法构建ComparableOR对象
     *
     * @param left
     * @param right
     * @return
     */
    private boolean checkOrOperatorHasSameColumnInLeftAndRight(SQLExpr left, SQLExpr right) {
        return Objects.equal(buildColumnIgnoreAlias(left), buildColumnIgnoreAlias(right));
    }

    private Column buildColumnIgnoreAlias(SQLExpr expr) {
        return SQLParserUtil.buildColumnIgnoreAlias(expr, parseContext.getSqlParsedResult().routeContext.getTables());
    }

    private ComparableUnit createComparableUnit(SQLExpr expr, BinaryOperator binaryOperator) {
        if (expr instanceof SQLVariantRefExpr) {
            //占位符,使用BindingValue表示
            return new ComparableUnit(new BindingValue(((SQLVariantRefExpr) expr).getIndex()), binaryOperator, getParameters());
        } else if (expr instanceof SQLNumericLiteralExpr) {
            try {
                Long longValue = Long.valueOf(expr.toString());
                return new ComparableUnit(longValue, binaryOperator, getParameters());
            } catch (NumberFormatException e) {
                return new ComparableUnit((Comparable) (expr.toString()), binaryOperator, getParameters());
            }
        } else if (expr instanceof SQLIntegerExpr) {
            return new ComparableUnit(Integer.valueOf(expr.toString()), binaryOperator, getParameters());
        }  else if (expr instanceof SQLCharExpr) {
            return new ComparableUnit((Comparable) (expr.toString()), binaryOperator, getParameters());
        }else if (expr instanceof Comparable || expr == null) {
            //这个判断是不是不需要?
            return new ComparableUnit((Comparable) (expr.toString()), binaryOperator, getParameters());
        } else {
            throw new IllegalArgumentException("Only support BindingValue or Comparable ==> Illegal type: " + expr.getClass().getName());
        }
    }

    @Override
    public ParseContext getParseContext() {
        return parseContext;
    }

    @Override
    public SQLBuilder getSQLBuilder() {
        return (SQLBuilder) appender;
    }

    @Override
    public void printReplaceToken(String replaceToken) {
        getSQLBuilder().appendReplaceToken(replaceToken);
    }

    @Override
    public void registerColumnAppendToken() {
        getSQLBuilder().registerColumnAppendToken();
    }

    @Override
    public void printExprSkipToken() {
        getSQLBuilder().appendExprSkipToken();
    }
}
